数据模型
在Hbase中,数据同样是存储在表中,有行和列。但是hbase和关系型数据库(RDBMS)有很大的区别,hbase更像是多维度的map。
hbase的同样有着表(table), 行(row),列(Column),根据表、行、列可以定位到一个单元格(cell)上,这样看来hbase和关系型数据库形式一样,其实不是的,hbase的列包括列簇(column family)和列限定符(column qualifier),一般将列限定符成为列,一行有固定的列簇(由表结构决定),一个列簇包含一系列的列,列没有固定的结构,每一行的列簇下的列可以不相同,列由添加数据决定,可以将hbase的表想象成一个Map<row, Map<column family,Map
存储在HBase表中的行是按照行健的字典顺序排列下来的。
如下为一个hbase表中存储的数据,com.cnn.www的people列簇可以不存储任何列和值,com.cnn.www和com.example.www列簇中的列可以不相同
Row Key | Time Stamp | ColumnFamily contents |
ColumnFamily anchor |
ColumnFamily people |
---|---|---|---|---|
“com.cnn.www” | t9 | anchor:cnnsi.com = “CNN” | ||
“com.cnn.www” | t8 | anchor:my.look.ca = “CNN.com” | ||
“com.cnn.www” | t6 | contents:html = “…” | ||
“com.cnn.www” | t5 | contents:html = “…” | ||
“com.cnn.www” | t3 | contents:html = “…” | ||
“com.example.www” | t5 | contents:html = “…” | people:author = “ |
实际存储
虽然在概念级别,表可以被视为稀疏的行集,但它们是按列族物理存储的。可以随时将新的列限定符(column_family:column_qualifier)添加到现有列族。如下anchor列簇的存储表和contents列簇的存储表。
行键 | 时间戳 | 列族 anchor |
---|---|---|
“com.cnn.www” | T9 | anchor:cnnsi.com = "CNN" |
“com.cnn.www” | T8 | anchor:my.look.ca = "CNN.com" |
行键 | 时间戳 | 列族contents: |
---|---|---|
“com.cnn.www” | T6 | contents:html =“ ……” |
“com.cnn.www” | T5 | contents:html =“ ……” |
“com.cnn.www” | T3 | contents:html =“ ……” |
概念视图中显示的空单元格根本不存储。因此,contents:html
在时间戳处对列的值的请求t8
将不返回任何值。同样,对anchor:my.look.ca
时间戳记值的请求t9
将不返回任何值。但是,如果未提供时间戳,则将返回特定列的最新值。给定多个版本,最新版本也是第一个版本,因为时间戳按降序存储。因此,com.cnn.www
如果未指定时间戳,则对行中所有列的值的请求将是:contents:html
来自timestamp t6
的值,anchor:cnnsi.com
来自timestamp t9
的值,anchor:my.look.ca
来自timestamp t8
的值。
存储的数据类型
HBase通过Put和Result支持“bytes-in / bytes-out”接口,实际存储的是字节数组, 因此任何可以转换为字节数组的都可以存储为值。输入可以是字符串,数字,复杂对象,甚至是图像,只要它们可以呈现为字节。
值的大小存在实际限制(例如,在HBase中存储10-50MB对象可能太大了)。HBase中的所有行都符合数据模型,包括版本控制。在进行设计时要考虑到这一点,以及ColumnFamily的块大小。
命名空间
命名空间是表的逻辑分组,类似于关系型数据库系统中的数据库。命名空间提供了一下几个好处(这里不太理解具体的体现):
- 配额管理(HBASE-8410) - 限制命名空间可以使用的资源量(即区域,表)。
- 命名空间安全管理(HBASE-9206) - 为租户提供另一级别的安全管理。
- 区域服务器组(HBASE-6721) - 可以将命名空间/表固定到RegionServers的子集上,从而保证粗略的隔离级别。
命名空间管理
可以创建,删除或更改命名空间。在表创建期间通过指定表单的完全限定表名来确定命名空间成员身份。
1 | <table namespace>:<table qualifier> |
1 | #Create a namespace |
预定义的名称空间
有两个预定义的特殊命名空间:
- hbase - 系统命名空间,用于包含HBase内部表
- default - 没有明确指定名称空间的表将自动落入此名称空间
1 | #namespace=foo and table qualifier=bar |
Hbase shell的基本命令(持续更新)
==hbase shell命令是对大小写敏感,写入命令要注意区分大小写。==
list 命令
列出所有的表名
describe命令
查看表的信息
1
describe 'table_name'
create命令
例如创建一个名为t1,列簇有f1,f2,f3的表,行健是必须的所以不许指定,每个列簇的列添加数据时指定,因为每一行数据的列都不相同。
1
2
3
4
5create 't1', {NAME => 'f1'}, {NAME => 'f2'}, {NAME => 'f3'}
# The above in shorthand would be the following:
create 't1', 'f1', 'f2', 'f3'
create 't1', {NAME => 'f1', VERSIONS => 1, TTL => 2592000, BLOCKCACHE => true}
create 't1', {NAME => 'f1', CONFIGURATION => {'hbase.hstore.blockingStoreFiles' => '10'}}enable,disable、is_disabled、disable_all命令
enable,disable分别是解除禁用和禁用表,禁用表之后可以看到该表的存在,但是无法对该表的内容进行操作,is_disabled查看该表是否被禁用,(enable_all)disable_all(解除)禁用所有匹配给定正则表达式的表。
1
2
3
4disable 'table_name'
is_disable 'table_name'
# 禁用所有表名前缀为table的表
disable_all 'table*'alter命令
修改表结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36# 改变或添加列簇的最带版本存储的版本个数为5
alter 't1', NAME => 'f1', VERSIONS => 5
# 能同时修改多个列簇
alter 't1', 'f1', {NAME => 'f2', IN_MEMORY => true}, {NAME => 'f3', VERSIONS => 5}
# 删除ns1命名空间下的t1表的f1列簇
alter 'ns1:t1', NAME => 'f1', METHOD => 'delete'
alter 'ns1:t1', 'delete' => 'f1'
# 能修改表的MAX_FILESIZE, READONLY, MEMSTORE_FLUSHSIZE, DURABILITY等属性,比如我们修改region的最大大小至128M
alter 't1', MAX_FILESIZE => '134217728'
# 可以通过设置表协处理器属性来添加表协处理器:
alter 't1',
'coprocessor'=>'hdfs:///foo.jar|com.foo.FooRegionObserver|1001|arg1=1,arg2=2'
# 由于您可以为表配置多个协处理器,因此序列号将自动附加到属性名称以唯一标识它。
# 协处理器属性必须与下面的模式匹配,以便框架了解如何加载协处理器类:
# [coprocessor jar file location] | class name | [priority] | [arguments]
# 可以设置configuration属性在表或列簇上
alter 't1', CONFIGURATION => {'hbase.hregion.scan.loadColumnFamiliesOnDemand' => 'true'}
alter 't1', {NAME => 'f2', CONFIGURATION => {'hbase.hstore.blockingStoreFiles' => '10'}}
# 可以删除表范围属性
alter 't1', METHOD => 'table_att_unset', NAME => 'MAX_FILESIZE'
alter 't1', METHOD => 'table_att_unset', NAME => 'coprocessor$1'
# 可以设置REGION_REPLICATION:
alter 't1', {REGION_REPLICATION => 2}
# 可以同时修改多个属性
alter 't1', { NAME => 'f1', VERSIONS => 3 },
{ MAX_FILESIZE => '134217728' }, { METHOD => 'delete', NAME => 'f2' },
OWNER => 'johndoe', METADATA => { 'mykey' => 'myvalue' }drop命令
删除表,在删除表之前要确保表已经disbale,这样可以防止删除表时对其他使用该表的用户造成的影响
1
2hbase>disable 'table_name'
hbase>drop 'table_name'put命令
例如向表t1的行健为r1的列簇为f1,列名为q1的值设为value,当存在是会设置新的时间戳的值,不存在会添加新的列。(如果不指定列名,则默认列名为空字符串)
1
2
3
4
5
6put 'ns1:t1', 'r1', 'f1:q1', 'value'
put 't1', 'r1', 'f1:q1', 'value'
put 't1', 'r1', 'f1:q1', 'value', ts1
put 't1', 'r1', 'f1:q1', 'value', {ATTRIBUTES=>{'mykey'=>'myvalue'}}
put 't1', 'r1', 'f1:q1', 'value', ts1, {ATTRIBUTES=>{'mykey'=>'myvalue'}}
put 't1', 'r1', 'f1:q1', 'value', ts1, {VISIBILITY=>'PRIVATE|SECRET'}get 命令
查看表中的数据
1
2
3
4
5
6# 查看t1表的r1行的相关内容
get 't1', 'r1'
# 查看t1表的r1行的列簇为c1相关内容
get 't1', 'r1', 'f1'
# 查看t1表的r1行的列簇为c1的列为cnam相关内容
get 't1', 'r1', 'f1:q1'scan 命令
扫描表,没有像put和get简便的写法
1
2
3
4
5
6
7# 查看表"t1"中的所有数据
scan 't1'
# scan 命令可以指定 startrow,stoprow 来 scan 多个 row
scan 'user_test',{COLUMNS =>'info:username',LIMIT =>10, STARTROW => 'test', STOPROW=>'test2'}
# 查看表"t1"中列族"c1"的所有数据,或者列族"c1"中列"cname"的所有数据
scan 't1', {COLUMN => 'f1'}
scan 't1', {COLUMN => 'f1:q1'}delete 命令
1
delete 't1', 'r1', 'f1:q1'
可以将命令写入脚本文件一次性顺序执行,例如在hbase_commands.txt中写入要执行的hbase shell命令,可以使用如下命令执行,在脚本的结尾最好写入exit命令,否则执行完命令之后停留在客户端交互界面。
1 | hbase shell ./hbase_commands.txt |
HBase 的API(持续更新)
HBase与表操作有关的api主要有
- HBaseConfiguration(配置类),
- Connection(建立数据库连接类),
- Admin(操作表的创建,删除,修改),
- TableName(表名类),Table(操作表内容的增删改查),
- Put(对应于put命令,存储值,Table.put方法的参数),
- Get(对应于get命令,查看值,Table.get方法的参数),
- Append(在值后面追加内容,table.append方法的参数,若原本有内容,得到追加后的结果,否则返回null),
- Result(get返回的对象),
- Scan(对应于scab命令,遍历值,Table.scan方法的参数,scan在指定起始行健和终止行健时,扫描的范围是[startRow, endRow),不包括终止行),
- ResultScanner(scan的结果返回ResultScanner对象,扫描不会通过一次RPC请求到所有的结果,而是按行为单位请求返回的,如果请求大量数量,一次请求完会消耗大量系统资源和时间,返回的ResultScanner对象是一个迭代器,迭代得到Result对象,每次调用next方法会进行RPC请求一行数据,即使next(int nbRows)方法内部也是调用多次next()方法)
- Delete(对应于delete命令,删除值,Table.delete方法的参数,在没有指定时间戳的情况下,deleteColumn方法删除列的最新版本,deleteColumns方法删除所有版本,在指定时间戳的情况下,deleteColumn删除与时间戳匹配的版本,deleteColumns方法删除与时间戳相等和更早的版本)。
Put,Get,Delete, Increment,Append等都是Row的子类,方便Tbale的batch(List<? extends Row> actions, Object[] results)方法批处理增删改查等操作
api的详细使用请查看官方文档http://hbase.apache.org/1.2/apidocs/,这是1.2版本的,目前对于新版本的HBase来说有些过时。
写的简单操作hbase表的类 https://github.com/Yhaij/HadoopLearn/tree/master/src/main/java/com/hbase/learn
行锁
HBase提供行锁,但是在0.95版本之后客户端无法显示的得到和使用行锁,该API已经移除,只在服务端有行锁的应用。在客户端只能使用checkAndPut,checkAndDelete等行原子操作
https://issues.apache.org/jira/browse/HBASE-7315
rowkey的设计原则和热点问题
长度原则,最短越好,最大不能超过64K。太长的影响有两点,一是极大影响了HFile的存储效率。二是缓存memstore不能得到有效利用,缓存不能存放太多的信息,造成检索效率的降低。
唯一原则:保证rowkey的唯一性
尽量保证经常一起用的rowkey存储在同一个region上,有助于提升检索效率。但要避免热点问题。
热点问题:即有的region存储大量的行记录,而有些只有少数,这是大量rowkey前缀相同,排序紧紧挨着一起造成
解决方法:
- 加盐:在rowkey前面加一个冗余信息,这样可以把数据分散到不同的region中
- 优点:可以有效的防止rowkey集中分配到一个或多个region中。有效避免了热点问题
- 缺点:无形中增加了rowkey的长度;范围检索得不到有效使用。
- 字段交换,提升权重:如果rowkey中含有几个信息字段,可以调整信息字段的顺序。
- 缺点:对于单个信息字段,或者无论怎么调整都会遇到region热点的rowkey是解决不了的
- 随机键:把rowkey进行hash化,在分配到不同的服务器上。和加盐的方式相似;
原理
如何找到数据的Region Server : 通过zookeeper找到.META.的Region Server,cache到客户端,接着在对应的Region Server中找到.META.,同时cache下来,在.META.表中找到对应的数据的RS.
HBase写逻辑:先找到要写入的RS,先写入到HLog,在写入到MemStore
HBase读逻辑:先找到记录的RS和Region,先顺序遍历MemStore查看是否有该条记录,没有再在HFile中通过布隆过滤器判断是否在文件中,有则在其中查找(根据索引来查找,是多层的,相当于一棵树,多层原因是可以各层加载进内存,不需要加载整个索引)
MemStore刷盘触发时机:
- 全局内存控制:当所有memstore占整个heap的最大比例(默认40%)的时候,会触发刷盘的操作,当刷盘时memstore小于整个heap的某个比例时(默认35%),停止。减少刷盘对业务带来的影响,实现平滑系统负载的目的。
- MemStore达到上限:当MemStore的大小达到hbase.hregion.memstore.flush.size大小的时候会触发刷盘,默认128M大小
RegionServer的Hlog数量达到上限:前面说到Hlog为了保证Hbase数据的一致性,那么如果Hlog太多的话,会导致故障恢复的时间太长,因此Hbase会对Hlog的最大个数做限制。当达到Hlog的最大个数的时候,会强制刷盘。这个参数是hase.regionserver.max.logs,默认是32个。
手动触发:可以通过hbase shell或者java api手工触发flush的操作。
- 关闭RegionServer触发:在正常关闭RegionServer会触发刷盘的操作,全部数据刷盘后就不需要再使用Hlog恢复数据。
- Region使用HLOG恢复完数据后触发:当RegionServer出现故障的时候,其上面的Region会迁移到其他正常的RegionServer上,在恢复完Region的数据后,会触发刷盘,当刷盘完成后才会提供给业务访问。
MemStore会按照排序好的结果刷盘进storefile,当storefile过大会触发Compaction, Compaction分为两类:MinorCompaction 和 MajorCompaction。MinorCompaction将小的,相邻的storefile合并,MajorCompaction将所有的storefile合成一个storefile。
为什么要Compaction:因为Hadoop不擅长处理小文件,文件越大性能越好。